from logging import getLogger

from zope.component import getGlobalSiteManager

from twisted.internet.processes import deferToProcess
from twisted.internet.threads import deferToThread
from twisted.scheduling.cron import CronSchedule

from nevow.athena import LiveElement, expose
from nevow.loaders import stan
from nevow import tags

from meercat.harvester.base.interfaces import IHarvester, IQueriableHarvester
from meercat.storage.interfaces import IStorage

from meercat.harvester.base import job as harvestJobs
from meercat.solr import job as solrJobs

from meercat.job.interfaces import IJob



__all__ = ['IncrementalHarvestJobForm',
           'IncrementalSolrJobForm',
           'ScheduledHarvestJobForm',
           'ScheduledSolrJobForm',
           'JobTypeForm']

log = getLogger('meercat.server.nevow.elements.job.forms')

registry = getGlobalSiteManager()

def getHarvestFormFields(filterQueriable=False):
    log.info(unicode(list(registry.getUtilitiesFor(IStorage))))
    log.info(unicode(list(registry.getUtilitiesFor(IHarvester))))
    
    if filterQueriable:
        f = lambda x: IQueriableHarvester.providedBy(x[1])
    else:
        f = lambda x: True
    
    return [
        tags.li()['Storage: ', tags.select(name='storage')[
            map(lambda x: tags.option(value=x[0])[x[0]],
                registry.getUtilitiesFor(IStorage))]],
        tags.li()['Harvester: ', tags.select(name='harvester')[
            map(lambda x: tags.option(value=x[0])[x[0]],
                filter(f, registry.getUtilitiesFor(IHarvester)))]]]

def validateHarvestFormFields(data, requireQueriable=False):
    data['storage'] = registry.queryUtility(IStorage, data['storage'])
    data['harvester'] = registry.queryUtility(IHarvester, data['harvester'])
    
    if requireQueriable:
        if not IQueriableHarvester.providedBy(data['harvester']):
            data['harvester'] = None
    
    if data['storage'] is None or data['harvester'] is None:
        raise ValueError('Invalid storage or harvester')
    
    return data

def getSolrFormFields():
    return [
        tags.li()['Storage: ', tags.select(name='storage')[
            map(lambda x: tags.option(value=x[0])[x[0]],
                registry.getUtilitiesFor(IStorage))]],
        tags.li()['Solr Base URL: ', tags.input(name='url',type='text')]]

def validateSolrFormFields(data):
    errors = []
    
    data['storage'] = registry.queryUtility(IStorage, data['storage'])
    
    if data['storage'] is None:
        raise ValueError('Invalid storage')
    
    return data

def getIncrementalFormFields():
    return [
        tags.li()['Limit: ', tags.input(name='limit', type='text')],
        tags.li()['Offset: ', tags.input(name='offset', type='text')],
        tags.li()['Step: ', tags.input(name='step', type='text')],
        tags.li()['Max Workers: ', tags.input(name='max_workers', type='text')],]

def validateIncrementalFormFields(data):
    data['limit'] = data['limit'].strip()
    data['offset'] = data['offset'].strip()
    data['step'] = data['step'].strip()
    data['max_workers'] = data['max_workers'].strip()
    
    if data['limit'] != '':
        data['limit'] = int(data['limit'])
    else:
        del data['limit']
    
    if data['offset'] != '':
        data['offset'] = int(data['offset'])
    else:
        del data['offset']
    
    if data['max_workers'] != '':
        data['max_workers'] = int(data['max_workers'])
        assert data['max_workers'] > 0
    else:
        del data['max_workers']
    
    data['step'] = int(data['step'])
    
    assert data['step'] > 0
    
    return data

def getScheduleFormFields():
    return [
        tags.li()['Cron Schedule: ', tags.input(name='schedule',type='text')],
        tags.li()['From Date: ', tags.input(name='from_date', type='text')],]

def validateScheduleFormFields(data):
    data['schedule'] = CronSchedule(data['schedule'])
    
    return validateDate(data)

def validateDate(data, field='from_date', errors=True):
    from datetime import datetime
    
    strptime = datetime.strptime
    
    data[field] = data[field].strip()
    
    if data[field] != '':
        formatStrings = ['%Y-%m-%d %H:%M:%S',
                         '%Y-%m-%d %H:%M',
                         '%Y-%m-%d %H',
                         '%Y-%m-%d',
                         '%Y/%m/%d %H:%M:%S',
                         '%Y/%m/%d %H:%M',
                         '%Y/%m/%d %H',
                         '%Y/%m/%d']
        
        for format in formatStrings:
            try:
                temp = strptime(data[field], format)
            except ValueError:
                continue
            else:
                data[field] = temp
                return data
        
        if errors:
            raise ValueError('%s did not match any of the format strings' % (data[field]))
        else:
            del data[field]
    else:
        del data[field]
    
    return data

def validateDuration(data, field='duration', errors=True):
    import re
    
    data[field] = data[field].strip()
    match = None
    
    if data[field] != '':
        match = re.match(r'^((?P<hours>\d+)h)?\s*((?P<minutes>\d+)m)?\s*((?P<seconds>\d+)s)?$',
                         data[field])
    
    if match is not None:
        from datetime import timedelta
        
        kwargs = {}
        
        for k,v in match.groupdict({'hours':0,'minutes':0,'seconds':0}).items():
            kwargs[k] = int(v)
        
        data[field] = timedelta(**kwargs)
    else:
        if errors:
            raise ValueError('%s is not a valid duration' % (data[field]))
        else:
            del data[field]
    
    return data

def validateTime(data, field='start', errors=True):
    import re
    
    match = re.match(r'^(?P<hour>\d{1,2}):(?P<minute>\d{1,2})$', data[field])
    
    if match is not None:
        from datetime import time
        
        data[field] = time(*map(lambda x: int(x),match.groups()))
    else:
        if errors:
            raise ValueError('%s is not a valid time' % (data[field]))
        else:
            del data[field]
    
    return data

class IncrementalHarvestJobForm(LiveElement):
    jsClass = u'Meercat.SubForm'
    
    @property
    def docFactory(self):
        return stan(tags.div(class_='jobForm',
                             render=tags.directive('liveElement'))[
            tags.form()[
                tags.ul()[
                    tags.li()['Name: ', tags.input(name='name', type='text')],
                    getHarvestFormFields(True),
                    getIncrementalFormFields(),
                    tags.li()['Downtime Start: ', tags.input(name='downtime_begin', type='text')],
                    tags.li()['Downtime Duration: ', tags.input(name='downtime_duration', type='text')],
                    tags.input(value="Add Job", type="button",
                               onclick="Nevow.Athena.Widget.get(this).submit();",)]]])
    
    @expose
    def submit(self, **data):
        log.info('%s.submit(%s)' % (str(self),str(data)))
        
        name = data['name']
        del data['name']
        
        data = validateHarvestFormFields(data, True)
        data = validateIncrementalFormFields(data)
        data = validateTime(data, 'downtime_begin',False)
        data = validateDuration(data, 'downtime_duration',False)
        
        data['deferredFactory'] = deferToProcess
        
        if not 'downtime_begin' in data and not 'downtime_duration' in data:
            job = harvestJobs.IncrementalHarvestJob(**data)
        elif 'downtime_begin' in data and 'downtime_duration' in data:
            job = harvestJobs.IncrementalDailyDowntimeHarvestJob(**data)
        else:
            raise ValueError('Either both downtime start and duration must be set or neither')
        
        registry.registerUtility(component=job, name=name, provided=IJob)
        self.page.getJobService().add(job)
        
        return True

class ScheduledHarvestJobForm(LiveElement):
    jsClass = u'Meercat.SubForm'
    
    @property
    def docFactory(self):
        return stan(tags.div(class_='jobForm',
                             render=tags.directive('liveElement'))[
            tags.form()[
                tags.ul()[
                    tags.li()['Name: ', tags.input(name='name', type='text')],
                    getHarvestFormFields(),
                    getScheduleFormFields(),
                    tags.input(value="Add Job", type="button",
                               onclick="Nevow.Athena.Widget.get(this).submit();",)]]])
    
    @expose
    def submit(self, **data):
        log.info('%s.submit(%s)' % (str(self),str(data)))
        name = data['name']
        del data['name']
        
        data = validateHarvestFormFields(data)
        data = validateScheduleFormFields(data)
        
        data['deferredFactory'] = deferToProcess
        
        if IQueriableHarvester.providedBy(data['harvester']):
            job = harvestJobs.ScheduledUpdateHarvestJob(**data)
        elif not 'from_date' in data:
            job = harvestJobs.ScheduledFullHarvestJob(**data)
        else:
            raise ValueError('From date cannot be specified on scheduled full harvests')
        
        registry.registerUtility(component=job, name=name, provided=IJob)
        self.page.getJobService().add(job)
        
        return True

class IncrementalSolrJobForm(LiveElement):
    jsClass = u'Meercat.SubForm'
    
    @property
    def docFactory(self):
        return stan(tags.div(class_='jobForm',
                             render=tags.directive('liveElement'))[
            tags.form()[
                tags.ul()[
                    tags.li()['Name: ', tags.input(name='name', type='text')],
                    getSolrFormFields(),
                    getIncrementalFormFields(),
                    tags.li()['Source: ', tags.input(name='source', type='text')],
                    tags.input(value="Add Job", type="button",
                               onclick="Nevow.Athena.Widget.get(this).submit();",)]]])
    
    @expose
    def submit(self, **data):
        log.info('%s.submit(%s)' % (str(self),str(data)))
        name = data['name']
        del data['name']
        
        data = validateSolrFormFields(data)
        data = validateIncrementalFormFields(data)
        
        data['source'] = data['source'].strip()
        
        if data['source'] == '':
            del data['source']
        
        data['deferredFactory'] = deferToProcess
        data['timeout'] = 15.0
        
        job = solrJobs.IncrementalSolrJob(**data)
        
        registry.registerUtility(component=job, name=name, provided=IJob)
        self.page.getJobService().add(job)
        
        return True

class ScheduledSolrJobForm(LiveElement):
    jsClass = u'Meercat.SubForm'
    
    @property
    def docFactory(self):
        return stan(tags.div(class_='jobForm',
                             render=tags.directive('liveElement'))[
            tags.form()[
                tags.ul()[
                    tags.li()['Name: ', tags.input(name='name', type='text')],
                    getSolrFormFields(),
                    getScheduleFormFields(),
                    tags.input(value="Add Job", type="button",
                               onclick="Nevow.Athena.Widget.get(this).submit();",)]]])
    
    @expose
    def submit(self, **data):
        log.info('%s.submit(%s)' % (str(self),str(data)))
        name = data['name']
        del data['name']
        
        data = validateSolrFormFields(data)
        data = validateScheduleFormFields(data)
        
        data['deferredFactory'] = deferToProcess
        data['timeout'] = 15.0
        
        job = solrJobs.ScheduledSolrJob(**data)
        
        registry.registerUtility(component=job, name=name, provided=IJob)
        self.page.getJobService().add(job)
        
        return True

class JobTypeForm(LiveElement):
    jsClass = u'Meercat.Job.Types'
    
    jobs = {'Incremental Harvest Job': IncrementalHarvestJobForm,
            'Scheduled Harvest Job': ScheduledHarvestJobForm,
            'Incremental Solr Job': IncrementalSolrJobForm,
            'Scheduled Solr Job': ScheduledSolrJobForm}
    
    @property
    def docFactory(self):
        return stan(tags.div(class_='yui-g',
                             render=tags.directive('liveElement'))[
            tags.h1()['Add a Job'],
            tags.ul(class_='JobFormActions actionList')[
                tags.li()[
                    tags.a(href='#',
                           onclick="Nevow.Athena.Widget.get(this).remove(); return false;")['Close']]],
            tags.div(class_='jobTypeForm')[
                tags.h2()['Select Job Type'],
                tags.form()[
                    tags.ul()[
                        tags.li()[tags.select(name='type',
                                              onchange="Nevow.Athena.Widget.get(this).selectType();")[
                            tags.option(value='')['Select Job Type'],
                            map(lambda x: tags.option(value=x)[x],
                                sorted(self.jobs.keys()))]]]]]])
    
    @expose
    def getJobForm(self, type):
        if not type:
            return False
        
        if not type in self.jobs:
            raise ValueError('Unkown job type')
        
        form = self.jobs[type]()
        form.setFragmentParent(self)
        
        return form